package crypto

import (
	"io"
	"fmt"
	"bytes"
	"sort"
	"errors"
	"strings"
	"net/http"
	"io/ioutil"
	"crypto/aes"
	"crypto/rand"
	"crypto/sha1"
	"crypto/cipher"
	"encoding/base64"
	"encoding/binary"
	"encoding/xml"

	"read.com/wxmp/util"
)

/*** 解密 start ***/

// 解析XML结构体
type EncryptRequestBody struct {
	XMLName    xml.Name `xml:"xml"`
	ToUserName string   `xml:"ToUserName"`
	Encrypt    string   `xml:"Encrypt"`
}

func ParseEncryptRequestBody(r *http.Request) *EncryptRequestBody {
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		fmt.Println("read http body error.", err)
	}
	requestBody := &EncryptRequestBody{}
	err = xml.Unmarshal(body, requestBody)

	if err != nil {
		fmt.Println("aes Unmarshal error.", err)
	}
	return requestBody
}

func CheckSignatureAgain(r *http.Request, e *EncryptRequestBody, token string) bool {
	msgSignature := r.FormValue("msg_signature")
	timestamp := r.FormValue("timestamp")
	nonce := r.FormValue("nonce")
	encryptType := e.Encrypt
	sign := BuildSign([]string{token, timestamp, nonce, encryptType})
	return msgSignature == sign
}

func AesKey(encodingKey string) []byte {
	data, _ := base64.StdEncoding.DecodeString(encodingKey + "=")
	return data
}

func AesDecrypt(cipherData []byte, aesKey []byte) ([]byte, error) {
	k := len(aesKey)
	if len(cipherData)%k != 0 {
		return nil, errors.New("cipherData长度不是aesKey的整倍数")
	}
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		return nil, err
	}
	iv := make([]byte, aes.BlockSize)
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return nil, err
	}

	blockMode := cipher.NewCBCDecrypter(block, iv)
	plainData := make([]byte, len(cipherData))
	blockMode.CryptBlocks(plainData, cipherData)
	return plainData, nil
}

func ParseEncryptTextRequestBody(plainText []byte, appID string) (map[string]string, error) {
	// 读取长度
	buf := bytes.NewBuffer(plainText[16:20])
	var length int32
	binary.Read(buf, binary.BigEndian, &length)

	// 验证appID
	appIdStart := 20 + length
	id := plainText[appIdStart:int(appIdStart)+len(appID)]
	if string(id) != appID {
		return nil, errors.New("无效的appID")
	}

	// 最终xml包解析成 map
	return util.XmlToMap(plainText[20:20+length])
}

/*** 解密 end ***/

/*** 加密 start ***/

// 回复消息的结构体
type TextResponseBody struct {
	XMLName      xml.Name `xml:"xml"`
	ToUserName   CDATAText
	FromUserName CDATAText
	CreateTime   string
	MsgType      CDATAText
	Content      CDATAText
}

// 加密后的结构体
type EncryptResponseBody struct {
	XMLName      xml.Name `xml:"xml"`
	Encrypt      CDATAText
	MsgSignature CDATAText
	TimeStamp    string
	Nonce        CDATAText
}

type CDATAText struct {
	Text string `xml:",innerxml"`
}

// 将加密后的消息转为加密结构体
func MakeEncryptResponseBody(fromUserName, toUserName, content, nonce, timestamp, appToken, appId, aesKey string) ([]byte, error) {
	encryptBody := &EncryptResponseBody{}
	encryptXmlData, _ := makeEncryptXmlData(fromUserName, toUserName, timestamp, content, appId, aesKey)
	encryptBody.Encrypt = value2CDATA(encryptXmlData)
	encryptBody.MsgSignature = value2CDATA(makeMsgSignature(timestamp, nonce, encryptXmlData, appToken))
	encryptBody.TimeStamp = timestamp
	encryptBody.Nonce = value2CDATA(nonce)
	return xml.MarshalIndent(encryptBody, "", "")
}

// 回复消息的结构体转为xml，并加密
func makeEncryptXmlData(fromUserName, toUserName, timestamp, content, appId, aesKey string) (string, error) {

	// xml Encoding
	textResponseBody := &TextResponseBody{}
	textResponseBody.FromUserName = value2CDATA(fromUserName)
	textResponseBody.ToUserName = value2CDATA(toUserName)
	textResponseBody.MsgType = value2CDATA("text")
	textResponseBody.Content = value2CDATA(content)
	textResponseBody.CreateTime = timestamp
	body, err := xml.MarshalIndent(textResponseBody, "", "")

	if err != nil {
		return "", errors.New("xml marshal error")
	}

	// length bytes
	buf := new(bytes.Buffer)
	err = binary.Write(buf, binary.BigEndian, int32(len(body)))
	if err != nil {
		fmt.Println("Binary write err", err)
	}
	bodyLength := buf.Bytes()

	// random bytes
	randomBytes := []byte("abcdefghijklmnop")

	// appID
	plainData := bytes.Join([][]byte{randomBytes, bodyLength, body, []byte(appId)}, nil)
	cipherData, err := aesEncrypt(plainData, AesKey(aesKey))
	if err != nil {
		return "", errors.New("aesEncrypt error")
	}
	return base64.StdEncoding.EncodeToString(cipherData), nil
}

func value2CDATA(v string) CDATAText {
	return CDATAText{"<![CDATA[" + v + "]]>"}
}

func makeMsgSignature(timestamp, nonce, msg_encrypt, appToken string) string {
	sl := []string{appToken, timestamp, nonce, msg_encrypt}
	sort.Strings(sl)
	s := sha1.New()
	io.WriteString(s, strings.Join(sl, ""))
	return fmt.Sprintf("%x", s.Sum(nil))
}

func aesEncrypt(plainData []byte, aesKey []byte) ([]byte, error) {
	k := len(aesKey)
	if len(plainData)%k != 0 {
		plainData = PKCS7Pad(plainData, k)
	}
	block, err := aes.NewCipher(aesKey)
	if err != nil {
		fmt.Println("aesEncrypt error.", err)
		return nil, err
	}
	iv := make([]byte, aes.BlockSize)
	if _, err := io.ReadFull(rand.Reader, iv); err != err {
		return nil, err
	}
	cipherData := make([]byte, len(plainData))
	blockMode := cipher.NewCBCEncrypter(block, iv)
	blockMode.CryptBlocks(cipherData, plainData)
	return cipherData, nil
}

func PKCS7Pad(message []byte, blocksize int) (padded []byte) {
	// block size must be bigger or equal 2
	if blocksize < 1<<1 {
		panic("block size is too small (minimum is 2 bytes)")
	}
	// block size up to 255 requires 1 byte padding
	if blocksize < 1<<8 {
		// calculate padding length
		padlen := PadLength(len(message), blocksize)

		// define PKCS7 padding block
		padding := bytes.Repeat([]byte{byte(padlen)}, padlen)

		// apply padding
		padded = append(message, padding...)
		return padded
	}
	// block size bigger or equal 256 is not currently supported
	panic("unsupported block size")
}

func PadLength(sliceLength, blocksize int) (padlen int) {
	padlen = blocksize - sliceLength%blocksize
	if padlen == 0 {
		padlen = blocksize
	}
	return padlen
}

/*** 加密 end ***/
